#include "Game.h"


//constructor
Game::Game()
{
	//calls set up to initialize game state
	setup();
}
//setup game
void Game::setup()
{
	//init variables
	playerWantsToQuit = false;
	m_input = '0';
	m_updateRate = INIT_UPDATE_RATE;
	m_updateTimer = 0;
	m_age = 0;
	srand(time(NULL));
	m_lastTreasureSpawn = 0;
	m_score = 0;
	//select map size
	int mapNo = selectMapSize();
	switch(mapNo)
	{
	case 1:
		m_mapWidth = INIT_MAP_WIDTH_01;
		m_mapHeight = INIT_MAP_HEIGHT_01;
		m_scoreInc = SCORE_INC_01;
		break;
	case 2:
		m_mapWidth = INIT_MAP_WIDTH_02;
		m_mapHeight = INIT_MAP_HEIGHT_02;
		m_scoreInc = SCORE_INC_02;
		break;
	case 3:
		m_mapWidth = INIT_MAP_WIDTH_03;
		m_mapHeight = INIT_MAP_HEIGHT_03;
		m_scoreInc = SCORE_INC_03;
		break;
	case 4:
		m_mapWidth = INIT_MAP_WIDTH_04;
		m_mapHeight = INIT_MAP_HEIGHT_04;
		m_scoreInc = SCORE_INC_04;
		break;
	}	
	m_mapArea = m_mapWidth * m_mapHeight;
	//add snake's head
	SnakeSegment temp(m_mapWidth/2,m_mapHeight/2,DIRECTION_LEFT);
	snake.add(temp);
	//initialize old tail x&y
	//use for clearing the screen
	m_oldTailX = snake.getHead()->m_x+1;
	m_oldTailY = snake.getHead()->m_y+1;
	//create map
	m_map = new char * [m_mapWidth];
	for(int r = 0; r < m_mapWidth; r++)
	{
		m_map[r] = new char [m_mapHeight];
		//fill map
		for(int c = 0; c < m_mapHeight; c++)
		{		
			//top left
			if(r == 0 && c == 0)
				m_map[r][c] = char(201);
			//top right
			else if (r == m_mapWidth-1 && c == 0)
				m_map[r][c] = char(187);
			//bottom left
			else if (r == 0 && c == m_mapHeight-1)
				m_map[r][c] = char(200);
			//bottom right
			else if (r == m_mapWidth-1 && c == m_mapHeight-1)
				m_map[r][c] = char(188);
			//horizontal border
			else if (c == 0 || c == m_mapHeight-1)
				m_map[r][c] = char(205);
			//vertical border
			else if (r == 0 || r == m_mapWidth-1)
				m_map[r][c] = char(186);
			//everything else
			else
				m_map[r][c] = ' ';
		}
	}
}
//select the size of the playing area
int Game::selectMapSize()
{
	gotoxy(21, 1);
	printf("SNAKE");
	gotoxy(15, 2);
	printf("  by Aaron Cole");

	gotoxy(5,5);
	printf("Please select the size of your playing area:");
	gotoxy(15,7);
	printf("1 - %d x %d grid", INIT_MAP_WIDTH_01, INIT_MAP_HEIGHT_01);
	gotoxy(15,8);
	printf("2 - %d x %d grid", INIT_MAP_WIDTH_02, INIT_MAP_HEIGHT_02);
	gotoxy(15,9);
	printf("3 - %d x %d grid", INIT_MAP_WIDTH_03, INIT_MAP_HEIGHT_03);
	gotoxy(15,10);
	printf("4 - %d x %d grid", INIT_MAP_WIDTH_04, INIT_MAP_HEIGHT_04);
	int selection = 0;
	while(selection < 1 || selection > 4)
	{
		gotoxy(50,5);
		cin >> selection;
	}
	system("cls");
	return selection;
}
//FOR DEBUGGING
void Game::print(SnakeSegment &piece)
{
	printf("X: %d -- Y: %d", piece.m_x, piece.m_y);
	switch(piece.m_direction)
	{
	case DIRECTION_UP:
		printf(" -- UP");
		break;
	case DIRECTION_DOWN:
		printf(" -- DOWN");
		break;
	case DIRECTION_RIGHT:
		printf(" -- RIGHT");
		break;
	case DIRECTION_LEFT:
		printf(" -- LEFT");
		break;
	}
	printf("\n");
}

//FOR DEBUGGING
//cycle through and print the details for each segment
void Game::printAll()
{
	for(int i = 0; i < snake.size(); ++i)
	{
		printf("SEGMENT: %d -- ", i+1);
		print(*snake.getRawData(i));
	}
	printf("\n");
}

//add a new segment to the snake
void Game::add()
{
	SnakeSegment newPiece;

	switch(snake.getTail()->m_direction)
	{
	case DIRECTION_UP:
		newPiece.m_x = snake.getTail()->m_x;
		newPiece.m_y = snake.getTail()->m_y +1;
		break;
	case DIRECTION_DOWN:
		newPiece.m_x = snake.getTail()->m_x;
		newPiece.m_y = snake.getTail()->m_y -1;
		break;
	case DIRECTION_RIGHT:
		newPiece.m_x = snake.getTail()->m_x +1;
		newPiece.m_y = snake.getTail()->m_y;
		break;
	case DIRECTION_LEFT:
		newPiece.m_x = snake.getTail()->m_x -1;
		newPiece.m_y = snake.getTail()->m_y;
		break;
	}
	newPiece.m_direction = snake.getTail()->m_direction;
	snake.add(newPiece);
}


//move each segment individually and update its direction
int Game::move(int segmentNo, int dirOfPrevSegment)
{
	//save old direction
	int oldDirection = snake.getRawData(segmentNo)->m_direction;
	//move segment in its current direction
	switch(snake.getRawData(segmentNo)->m_direction)
	{
	case DIRECTION_UP:
		snake.getRawData(segmentNo)->m_y--;
		break;
	case DIRECTION_DOWN:
		snake.getRawData(segmentNo)->m_y++;
		break;
	case DIRECTION_RIGHT:
		snake.getRawData(segmentNo)->m_x--;
		break;
	case DIRECTION_LEFT:
		snake.getRawData(segmentNo)->m_x++;
		break;
	}
	//set direction equal to previous piece
	snake.getRawData(segmentNo)->m_direction = dirOfPrevSegment;
	//return old direction
	return oldDirection;
}

//cycle through and move each segment
void Game::moveAll()
{
	//save old tail location
	m_oldTailX = snake.getTail()->m_x;
	m_oldTailY = snake.getTail()->m_y;

	//create variable to store the direction for the next segment
	int dirOfNextPiece = snake.getHead()->m_direction;
	//move each segment
	for(int i = 0; i < snake.size(); ++i)
	{
		dirOfNextPiece = move(i, dirOfNextPiece);
	}
}

//draw the initial game screen
void Game::initDraw()
{
	//draw instructions
	gotoxy(m_mapWidth+6, 1);
	printf("CONTROLS");
	gotoxy(m_mapWidth+5,2);
	printf("----------");
	gotoxy(m_mapWidth+5,4);
	printf("w       UP");
	gotoxy(m_mapWidth+5,5);
	printf("a    RIGHT");
	gotoxy(m_mapWidth+5,6);
	printf("s     DOWN");
	gotoxy(m_mapWidth+5,7);
	printf("d     LEFT");
	gotoxy(m_mapWidth+5,8);
	printf("q     QUIT");
	//score
	gotoxy(m_mapWidth+5,12);
	printf("SCORE:");
	//draw the map
	for(int r = 0; r < m_mapWidth; r++)
	{
		for(int c = 0; c < m_mapHeight; c++)
		{
			gotoxy(r, c);
			printf("%c", m_map[r][c]);
		}
	}
}

//draw the game UI
void Game::draw()
{
	if(!playerWantsToQuit)//do not draw anything if the player decided to quit
	{
		//draw snakes head
		gotoxy(snake.getHead()->m_x, snake.getHead()->m_y);
		printf("@");
		//draw snakes body
		for(int i = 1; i < snake.size(); i++)
		{
			gotoxy(snake.getRawData(i)->m_x, snake.getRawData(i)->m_y);
			printf("*");
		}

		//clear previous locations of the snake
		gotoxy(m_oldTailX, m_oldTailY);
		printf("%c", m_map[m_oldTailX][m_oldTailY]);

		//draw treasures
		for(int n = 0; n < treasure.size(); n++)
		{
			gotoxy(treasure.getRawData(n)->m_x, treasure.getRawData(n)->m_y);
			printf("#");
		}

		//score
		gotoxy(m_mapWidth+5,13);
		printf("%d", m_score);
	}
}

//handle user input
bool Game::handleInput(int input)
{
	switch(input)
	{
	case 'q':
		gotoxy(1, m_mapHeight-2);
		playerWantsToQuit = true;
		return true;
		break;
	case 'w':
	case 'a':
	case 's':
	case 'd':
	//case 'l':
	//case 'k':
		m_input = input;
		return true;
		break;
	}
	return false;
}

//update the game state
void Game::update(int a_ms)
{
	m_updateTimer += a_ms;
	m_age += a_ms;

	if(m_updateTimer > m_updateRate)
	{
		moveAll();
		//printAll(); //DEBUG
		m_updateTimer = 0;
	}
	else
	{
		processInput();
	}

	//collision detection for walls and snake's body
	if(lethalCollisionCheck())
	{
		gameOver();
	}

	//generate 'treasures'	
	generateTreasure();
	//collision detection for 'treasures'
	treasureCollision();

	//increase score
	increaseScore(m_scoreInc);
}

//process user input
void Game::processInput()
{
	switch(m_input)
	{
	case 'w':
		//can't go in reverse
		if(snake.getHead()->m_direction != DIRECTION_DOWN || snake.size() == 1)
		{
			snake.getHead()->m_direction = DIRECTION_UP;
		}
		break;
	case 'a':
		if(snake.getHead()->m_direction != DIRECTION_LEFT || snake.size() == 1)
		{
			snake.getHead()->m_direction = DIRECTION_RIGHT;
		}
		break;
	case 's':
		if(snake.getHead()->m_direction != DIRECTION_UP || snake.size() == 1)
		{
			snake.getHead()->m_direction = DIRECTION_DOWN;
		}
		break;
	case 'd':
		if(snake.getHead()->m_direction != DIRECTION_RIGHT || snake.size() == 1)
		{
			snake.getHead()->m_direction = DIRECTION_LEFT;
		}
		break;
	//case 'k':
	//	add();
	//	break;
	//case 'l':
	//	moveAll();
	//	break;
	}
	m_input = '0'; //clear input buffer
}

//check for collision with walls or snakes body
bool Game::lethalCollisionCheck()
{
	//collision with border?
	if(snake.getHead()->m_x <= 0 || snake.getHead()->m_x >= m_mapWidth-1 || 
		snake.getHead()->m_y <= 0 || snake.getHead()->m_y >= m_mapHeight-1)
	{
		return true;
	}
	//collision with snake?
	else if(snake.size() > 1)//make sure there is a body to check for
	{
		for(int i = 1; i < snake.size(); i++)
		{
			if(snake.getHead()->m_x == snake.getRawData(i)->m_x &&
				snake.getHead()->m_y == snake.getRawData(i)->m_y)
			{
				return true;
			}
		}
	}
	return false;
}

//randomly generate treasures to make the snake grow
void Game::generateTreasure()
{
	bool createTreasure = false;
	//if(m_age > (m_lastTreasureSpawn + TREASURE_MIN_SPAWN_TIME))
	//spawn time is based off snake size and map area
	if(m_age > (m_lastTreasureSpawn + ((snake.size()*m_mapArea) + TREASURE_MIN_SPAWN_TIME)))
	{
		//if greater than min. spawn time, randomize spawn time
		if((rand() % 1000+1)%100)
		{createTreasure = true;}
	}
	if (m_age > (m_lastTreasureSpawn + ((snake.size()*m_mapArea) + TREASURE_MAX_SPAWN_TIME))) //if over the max spawn time
	{
		createTreasure = true;
	}

	if(createTreasure && treasure.size() < TREASURE_MAX_NO)//limit the number of treasures on screen at a time
	{
		//randomly generate the treasure's position within the fields limits
		int a_x, a_y;
		a_x = rand() % (m_mapWidth-TREASURE_CUSHION_MAX) + TREASURE_CUSHION_MIN;
		a_y = rand() % (m_mapHeight-TREASURE_CUSHION_MAX) + TREASURE_CUSHION_MIN;
		Nugget temp(a_x, a_y);
		treasure.add(temp);
		m_lastTreasureSpawn = m_age;
	}
}
//see if there is a collision with the treasure
void Game::treasureCollision()
{
	//for all treasures on screen
	for(int i = 0; i < treasure.size(); i++)
	{
		//if the snakes head collides with the treasure
		if(snake.getHead()->m_x == treasure.getRawData(i)->m_x && 
			snake.getHead()->m_y == treasure.getRawData(i)->m_y)
		{
			treasure.remove(i);//remove the treasure
			add();//increase snakes size
			increaseScore(m_scoreInc*SCORE_TREASURE);//increase score
		}
	}
}
//see if the player wants to retry
void Game::gameOver()
{
	//clear game screen
	system("cls");
	gotoxy(15, 1);
	printf("GAME OVER");

	gotoxy(13,9);
	printf("Final Score:");
	gotoxy(16,10);
	printf("%d", m_score);

	gotoxy(15,4);
	printf("Try again?");
	gotoxy(12,7);
	printf("Enter Y or N:");
	char selection = 0;//'0';
	while(selection != 'y' || selection != 'n')
	{
		gotoxy(26,7);
		cin >> selection;
		if(selection == 'y' || selection == 'Y')//if they want to retry?
		{
			//reload game
			system("cls");
			release();//delete game data
			setup();//init new game
			initDraw();//draw new game world
			break;
		}
		else if(selection == 'n' || selection == 'N')//if they want to quit
		{
			playerWantsToQuit = true;
			break;
		}
	}

}
//"thanks for playing"
void Game::thanks()
{
	system("cls");
	gotoxy(12,7);
	printf(" %c Thanks for playing %c", char(1), char(1));
	gotoxy(0,24);
}
//release memory
void Game::release()
{
	//delete snake;
	snake.clear();

	//delete treasures
	treasure.clear();

	//delete map
	for(int i = 0; i < snake.size(); i++)
	{
	}

	for(int r = 0; r < m_mapWidth; r++)
	{
		delete m_map[r];
	}
	delete m_map;
}
//deconstructor
Game::~Game()
{
	//calls release function to clean up
	release();
}

//END
